iT邦幫忙

2023 iThome 鐵人賽

DAY 8
0
Software Development

Python十翼:與未來的自己對話系列 第 8

[Day08] 三翼 - property:核心原理與基本型態

  • 分享至 

  • xImage
  •  

三翼大綱

property實作有__get____set____delete__,所以是一種data descriptor。其具備有簡潔的語法能方便使用,而不用煩惱descriptor的實作細節。

  • [Day08]分享property核心原理及基本使用型態。
  • [Day09]舉一個實例,看看如何活用property

核心原理

propertysignature如下:

property(fget=None, fset=None, fdel=None, doc=None) -> property

可以將其想為一個class,並接受四個選擇性參數。以下我會以prop來稱呼由property建立的property instance

  • fget控制instance.prop時的行為。
  • fset控制instance.prop = value時的行為。
  • fdel控制del instance.prop時的行為。
  • docprop的說明文件。如果沒有指定doc,而prop裡有fget時,會將doc設為fget中的__doc__
  • 每當我們建立prop後,想要新增fgetfsetfdel時,我們不mutate prop,而是透過property.getterproperty.setterproperty.deleter來新建一個prop返回。

基本型態

# 01property的基本型態。

# 01
class MyClass:
    def __init__(self, x):
        self.x = x

    @property
    def x(self):
        """docstrings from fget"""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x


if __name__ == '__main__':
    my_inst = MyClass(1)
  • 首先我們將property裝飾於function x上,而此function x內具有當使用my_inst.x語法時的行為。此裝飾相當於x = property(x)

    • function x作為property的第一個參數fget,並返回prop,且命名為x
    • 現在的prop x內已有fgetdoc,但還未有fsetfdel
  • 接著將x.setter裝飾於另一個function x上,而此function x內具有當使用my_inst.x = value語法時的行為。此裝飾相當於x = x.setter(x)

    • prop xsetter接收這個function x
    • setter中,將會生成一個新的prop,以第一步的fget作為fgetdoc作為doc,再加上剛剛接收的function x作為fset
    • 最後返回此新prop,且命名為x。現在的prop x內已有fgetfsetdoc,但還未有fdel
  • 最後將x.deleter裝飾於另一個function x上,而此function x內具有當使用del my_inst.x語法時的行為。此裝飾相當於x = x.deleter(x)

    • prop xdeleter接收這個function x
    • deleter中,將會生成一個新的prop,以上一步的fget作為fgetdoc作為docfset作為fset,再加上剛剛接收的function x作為fdel
    • 最後返回此新prop,且命名為x。現在的prop x內已完整擁有fgetfsetfdeldoc
  • 乍看之下,# 01只是生成了my_inst,還沒有任何與prop x互動。但仔細看看__init__my_inst已經透過property這個介面呼叫了prop xfset來進行self.x = x(self就是my_inst)。

  • @x.setter@x.deleter裝飾的function必須與@property所裝飾的function名一致,即x。如果使用不同名字,使用上會變得很困難,且容易出錯(註1)。

# 01是基本型態,但視您的程式需要,您可能需要先建立一個prop,然後再視情況加入fgetfsetfdeldoc,如# 01a

# 01a
class MyClass:
    def __init__(self, x):
        self.x = x

    prop = property()

    @prop.getter
    def x(self):
        """docstrings from fget"""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

又或者您可以建立好fgetfsetfdeldoc,再一次生成prop,如# 01b

# 01b
class MyClass:
    def __init__(self, x):
        self.x = x

    def get_x(self):
        return self._x

    def set_x(self, value):
        self._x = value

    def del_x(self):
        del self._x

    x = property(fget=get_x,
                 fset=set_x,
                 fdel=del_x,
                 doc="""docstrings""")

property適用時機

  • 當您於instance中有一個變數,不想直接被存取,而希望使用者透過給定的接口(gettersetterdeleter)來操作這個變數。由於使用property來存取變數,與存取一般變數的語法是相同的,所以我們寫code時,可以不用一開始就決定哪些變數要使用property,等到code的中後段再來判斷,而所有的interface並不需要因此修改。
  • 當一個變數值是需要動態計算而得,一般會實作一個function。但有些時候,這個變數更像是instance attribute,並且大多會使用快取的機制時,也是一個適合使用property的時機。

備註

註1:

  • @x.setter相當於set_x = x.setter(set_x),意思是使用prop xfgetdoc加上現在的set_x建立一個新prop返回,並命名為set_x。現在MyClass有兩個prop

    • 第一個是prop x,擁有fgetdoc
    • 第二個是prop set_x,擁有fgetfsetdoc
  • @x.deleter相當於del_x = x.deleter(del_x),意思是使用prop xfgetdoc加上現在的del_x建立一個新prop返回,並命名為del_x。現在MyClass有三個prop

    • 第一個是prop x,擁有fgetdoc
    • 第二個是prop set_x,擁有fgetfsetdoc
    • 第三個是prop del_x,擁有fgetfdeldoc
  • 儘管我們還是可以使用my_inst.x的語法,但:

    • my_inst.x = value必須改成my_inst.set_x = value
    • del my_inst.x必須改成del my_inst.delx
# 101
class MyClass:
    def __init__(self, x):
        self.set_x = x  # not self.x = x

    @property
    def x(self):
        """docstrings from fget"""
        return self._x

    @x.setter
    def set_x(self, value):
        self._x = value

    @x.deleter
    def del_x(self):
        del self._x


if __name__ == '__main__':
    my_inst = MyClass(1)
    for name, prop in (('x', MyClass.x),
                       ('set_x', MyClass.set_x),
                       ('del_x', MyClass.del_x)):
        print(f'prop {name=}: ')
        print(f'type of prop {name} is {type(prop)}')
        print(f'fget={prop.fget}')
        print(f'fset={prop.fset}')
        print(f'fdel={prop.fdel}')
        print(f'doc={prop.__doc__}\n')
prop name='x': 
type of prop x is <class 'property'>
fget=<function MyClass.x at 0x00000142E5AC4EA0>
fset=None
fdel=None
doc=docstrings from fget

prop name='set_x': 
type of prop set_x is <class 'property'>
fget=<function MyClass.x at 0x00000142E5AC4EA0>
fset=<function MyClass.set_x at 0x00000142E5AC4E00>
fdel=None
doc=docstrings from fget

prop name='del_x':
type of prop del_x is <class 'property'>
fget=<function MyClass.x at 0x00000142E5AC4EA0>
fset=None
fdel=<function MyClass.del_x at 0x00000142E5AC6340>
doc=docstrings from fget

Code

本日程式碼傳送門


上一篇
[Day07] 次翼 - Decorator:@func to class
下一篇
[Day09] 三翼 - property:實例說明
系列文
Python十翼:與未來的自己對話30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言